package editorsvg.editor.documento;

import editorsvg.editor.atributos.AtrTransformacion;
import editorsvg.editor.comandos.Comando;
import editorsvg.editor.comandos.ComandoTransformar;
import editorsvg.editor.vista.ConversorCoordenadas;
import editorsvg.propiedades.DefinicionPropiedades;
import editorsvg.utilidades.Angulo;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Dimension2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Jorge Berjano
 */
public class Seleccion implements Cloneable {

    private ConjuntoElementos elementos = new ConjuntoElementos();
    private static final int ladoGuia = 10;
    private Rectangle limites;
    private Rectangle limitesInicial;
    private Rectangle orejaNE = new Rectangle();
    private Rectangle orejaNO = new Rectangle();
    private Rectangle orejaSE = new Rectangle();
    private Rectangle orejaSO = new Rectangle();
    private ConversorCoordenadas conversor;
    private Direccion direccionEscalado;
    private Angulo anguloRotacion;
    private boolean modoRotacion = false;
    private AtrTransformacion transformacionActual = new AtrTransformacion();
    private boolean pintarContorno;

    public Seleccion(ConversorCoordenadas conversor) {
        this.conversor = conversor;
    }

    public ConjuntoElementos getElementos() {
        return elementos;
    }

    public boolean contiene(ElementoSVG elemento) {
        return elementos.contiene(elemento);
    }

    public boolean esModoRotacion() {
        return modoRotacion;
    }

    public void cambiarModo() {
        modoRotacion = !modoRotacion;
    }

    public ConjuntoElementos obtenerCopiaElementos() {
        try {
            return (ConjuntoElementos) elementos.clone();
        } catch (CloneNotSupportedException ex) {
            Logger.getLogger(Seleccion.class.getName()).log(Level.SEVERE,
                    "No se han podido clonar la lista de elementos de la selección", ex);
            return null;
        }
    }

    public AffineTransform obtenerCopiaTransformacion() {
        return (AffineTransform) transformacionActual.getAffineTransform().clone();
    }

    public DefinicionPropiedades obtenerPropiedades() {
        if (elementos.tieneElementos()) {
            return elementos.getLista().get(0).getPropiedades();
        }
        return null;
    }

    public void conmutar(ElementoSVG nodo) {
        if (!elementos.contiene(nodo)) {
            elementos.agregar(nodo);
        } else {
            elementos.eliminar(nodo);
        }
        actualizar();
    }

    public void seleccionar(ListaElementos listaNodos, boolean agregar) {
        if (!agregar) {
            this.elementos.limpiar();
        }
        this.elementos.getLista().addAll(listaNodos.getLista());
        actualizar();
    }

    public void seleccionar(ElementoSVG elemento, boolean agregar) {
        if (!elementos.contiene(elemento)) {
            if (!agregar) {
                elementos.limpiar();
            }
            elementos.agregar(elemento);
            actualizar();
        } else {
            cambiarModo();
        }
    }

    public void deseleccionar(ElementoSVG elemento) {
        elementos.borrar(elemento);
    }

    public Point2D getCentro() {
        Rectangle2D encuadre = elementos.getEncuadre();
        return new Point2D.Double(encuadre.getCenterX(), encuadre.getCenterY());
    }

    public Point2D getCentroPixels() {
        return new Point2D.Double(limites.getCenterX(), limites.getCenterY());
    }

    public void limpiar() {
        elementos.limpiar();
        setLimites(null);
        modoRotacion = false;
    }

    public void desplazarLimitesPixeles(int dx, int dy) {
        if (!haySeleccion()) {
            return;
        }
        limites.translate(dx, dy);
        actualizarOrejas();
    }

    public void actualizar() {
        if (haySeleccion() && elementos.getEncuadre() != null) {
            elementos.actualizarContorno();
            setLimites(conversor.aPixeles(elementos.getEncuadre()));
        } else {
            setLimites(null);
        }
    }

    public boolean haySeleccion() {
        return elementos.tieneElementos();
    }

    public boolean iniciarDesplazamiento(Point posicionActualRaton) {
        if (!haySeleccion()) {
            return false;
        }
        boolean iniciar = limites != null && limites.contains(posicionActualRaton);
        if (iniciar) {
            limitesInicial = (Rectangle) limites.clone();
            pintarContorno = true;
        }
        return iniciar;
    }

    public void actualizarDesplazamiento(int dx, int dy) {
        if (!haySeleccion()) {
            return;
        }

        limites.setBounds(limitesInicial);
        limites.translate(dx, dy);

        actualizarOrejas();

        transformacionActual.limpiar();
        final Dimension2D desplazamiento = conversor.aUnidadesReales(new Dimension(dx, dy));
        transformacionActual.desplazar(desplazamiento.getWidth(), desplazamiento.getHeight());
    }

    /**
     * Finaliza la acción de desplazar
     */
    public Comando finalizarDesplazamiento(int dx, int dy) {
        actualizarDesplazamiento(dx, dy);

        Comando comando = new ComandoTransformar(obtenerCopiaElementos(), obtenerCopiaTransformacion());

        transformacionActual.limpiar();
        pintarContorno = false;

        return comando;
    }

    /**
     * Comprueba si es se inicia la acción de escalar.
     * @param posicionRaton Posición actual del raton
     * @return True si se inicia el escalado.
     */
    public boolean iniciarEscalado(Point posicionRaton) {
        if (!haySeleccion() || esModoRotacion()) {
            return false;
        }
        if (orejaNE.contains(posicionRaton)) {
            direccionEscalado = Direccion.NE;
        } else if (orejaNO.contains(posicionRaton)) {
            direccionEscalado = Direccion.NO;
        } else if (orejaSE.contains(posicionRaton)) {
            direccionEscalado = Direccion.SE;
        } else if (orejaSO.contains(posicionRaton)) {
            direccionEscalado = Direccion.SO;
        } else {
            direccionEscalado = null;
        }

        if (direccionEscalado != null) {
            limitesInicial = (Rectangle) limites.clone();
            pintarContorno = true;
        }
        return direccionEscalado != null;
    }

    /**
     * Actualiza las guias durante el escalado.
     * @param dx Distancia que se arrastra el raton sobre del eje x.
     * @param dy Distancia que se arrastra el raton sobre del eje y.
     */
    public void actualizarEscalado(int dx, int dy) {
        switch (direccionEscalado) {
            case SE:
                limites.setLocation(limitesInicial.getLocation());
                limites.setSize(limitesInicial.width + dx, limitesInicial.height + dy);
                break;
            case SO:
                limites.setLocation(limitesInicial.x + dx, limitesInicial.y);
                limites.setSize(limitesInicial.width - dx, limitesInicial.height + dy);
                break;

            case NE:
                limites.setLocation(limitesInicial.x, limitesInicial.y + dy);
                limites.setSize(limitesInicial.width + dx, limitesInicial.height - dy);
                break;
            case NO:
                limites.setLocation(limitesInicial.x + dx, limitesInicial.y + dy);
                limites.setSize(limitesInicial.width - dx, limitesInicial.height - dy);
                break;
        }

        transformacionActual.limpiar();
        Rectangle2D encuadreInicial = conversor.aUnidadesReales(limitesInicial);
        Rectangle2D encuadreFinal = conversor.aUnidadesReales(limites);
        transformacionActual.escalar(encuadreInicial, encuadreFinal);

        actualizarOrejas();
    }

    /**
     * Finaliza la acción de escalar
     */
    public Comando finalizarEscalado(int dx, int dy) {
        actualizarEscalado(dx, dy);

        Comando comando = new ComandoTransformar(obtenerCopiaElementos(), obtenerCopiaTransformacion());

        transformacionActual.limpiar();
        pintarContorno = false;

        return comando;
    }

    public boolean iniciarRotacion(Point posicionRaton) {
        if (!haySeleccion() || !esModoRotacion()) {
            return false;
        }

        boolean iniciarRotacion = orejaNE.contains(posicionRaton) ||
                orejaNO.contains(posicionRaton) ||
                orejaSE.contains(posicionRaton) ||
                orejaSO.contains(posicionRaton);

        transformacionActual.limpiar();

        if (iniciarRotacion) {
            pintarContorno = true;
        }

        return iniciarRotacion;
    }

    public void actualizarRotacion(Angulo rotacion) {
        this.anguloRotacion = rotacion;

        transformacionActual.limpiar();
        transformacionActual.rotar(anguloRotacion, getCentro().getX(), getCentro().getY());
    }

    public Comando finalizarRotacion(Angulo rotacion) {
        actualizarRotacion(rotacion);

        Comando comando = new ComandoTransformar(obtenerCopiaElementos(), obtenerCopiaTransformacion());

        anguloRotacion = null;
        transformacionActual.limpiar();
        pintarContorno = false;

        return comando;
    }

    public void setConversor(ConversorCoordenadas conversor) {
        this.conversor = conversor;
    }

    public Rectangle getLimites() {
        return limites;
    }

    public void setLimites(Rectangle limites) {
        this.limites = limites;
        actualizarOrejas();
    }

    public void transformar(AffineTransform transformacion) {
        elementos.transformar(transformacion);
    }

    private void actualizarOrejas() {
        if (getLimites() == null) {
            return;
        }
        orejaNO.setRect(getLimites().x - ladoGuia, getLimites().y - ladoGuia, ladoGuia, ladoGuia);
        orejaNE.setRect(getLimites().x + getLimites().width, getLimites().y - ladoGuia, ladoGuia, ladoGuia);
        orejaSO.setRect(getLimites().x - ladoGuia, getLimites().y + getLimites().height, ladoGuia, ladoGuia);
        orejaSE.setRect(getLimites().x + getLimites().width, getLimites().y + getLimites().height, ladoGuia, ladoGuia);
    }

    public Shape getContorno() {
        return elementos.getContorno();
    }

    public void pintarEnPixeles(Graphics2D graphics) {
        if (!haySeleccion() || getLimites() == null) {
            return;
        }

        float[] dash = new float[2];
        dash[0] = 5;
        dash[1] = 5;
        graphics.setColor(Color.BLACK);
        graphics.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1, dash, 0));
        graphics.setXORMode(Color.WHITE);

        // Se pintan los limites de la seleccion
        graphics.draw(getLimites());

        graphics.setStroke(new BasicStroke(1));
        // Se pintan las orejas
        if (modoRotacion) {
            graphics.draw(orejaNE);
            graphics.draw(orejaNO);
            graphics.draw(orejaSE);
            graphics.draw(orejaSO);
        } else {
            graphics.fill(orejaNE);
            graphics.fill(orejaNO);
            graphics.fill(orejaSE);
            graphics.fill(orejaSO);
        }

        // Se pinta el contorno
        if (pintarContorno && transformacionActual != null && haySeleccion()) {
            Shape contornoTransformado = transformacionActual.transformar(getContorno());
            Shape contornoPixeles = conversor.transformarAPixeles(contornoTransformado);
            graphics.draw(contornoPixeles);
        }

        graphics.setPaintMode();
    }
}

